﻿using Microsoft.CSharp.Activities;
using System;
using System.Activities;
using System.Activities.Expressions;
using System.Activities.Presentation.PropertyEditing;
using System.Activities.Statements;
using System.Activities.Validation;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using WFExtended.Activities.Designers;
using WFExtended.Framework.Constraints;
using WFExtended.Framework.Presentation;
using WFExtended.Framework.ServiceModel;

namespace WFExtended.Activities
{
    /// <summary>
    /// Base class for a Wcf Operation Invoker Activity
    /// </summary>
    public abstract class AWcfOperationInvoker : NativeActivity
    {
        /// <summary>
        /// Get or Set the WCF Proxy
        /// </summary>
        public abstract InArgument<AWcfProxy> Proxy { get; set; }
    }

    /// <summary>
    /// Invoke a Operation Service Method
    /// <code>
    /// DelegateInArgument<WcfProxy<IMockService>> proxy = new DelegateInArgument<WcfProxy<IMockService>>(); <br />
    /// Activity workflow = new WcfConfigurationProxy<IMockService>() <br />
    /// &nbsp; { <br />
    /// &nbsp; &nbsp; Body = new System.Activities.ActivityAction<WcfProxy<IMockService>>() <br />
    /// &nbsp; &nbsp;  { <br />
    /// &nbsp; &nbsp; &nbsp; Argument = proxy, <br />
    /// &nbsp; &nbsp; &nbsp; Handler = new WcfOperationInvoker() <br />
    /// &nbsp; &nbsp; &nbsp; {<br /> 
    /// &nbsp; &nbsp; &nbsp; &nbsp; OperationName = "Ping",<br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Proxy = proxy <br />
    /// &nbsp; &nbsp; &nbsp; } <br />
    /// &nbsp; &nbsp; } <br /> 
    /// &nbsp; }; <br />
    /// </code>
    /// </summary>
    [ToolboxBitmapAttribute(typeof(EntryPoint), "Resources.WcfOperationInvokerToolbox.bmp")]
    [Designer(typeof(WcfOperationInvokerDesigner))]
    [Description("Invoke a Operation Service Method")]
    public class WcfOperationInvoker : WcfOperationInvoker<object>
    {
    }

    /// <summary>
    /// Invoke a Operation Service Method with a result
    /// <code>
    /// DelegateInArgument<WcfProxy<IMockService>> proxy = new DelegateInArgument<WcfProxy<IMockService>>(); <br />
    /// Variable<bool> result = new Variable<bool>(); <br />
    /// Activity workflow = new WcfConfigurationProxy<IMockService>() <br />
    /// &nbsp; { <br />
    /// &nbsp; &nbsp; Body = new System.Activities.ActivityAction<WcfProxy<IMockService>>() <br />
    /// &nbsp; &nbsp;  { <br />
    /// &nbsp; &nbsp; &nbsp; Argument = proxy, <br />
    /// &nbsp; &nbsp; &nbsp; Handler = new WcfOperationInvoker<bool>() <br />
    /// &nbsp; &nbsp; &nbsp; {<br /> 
    /// &nbsp; &nbsp; &nbsp; &nbsp; OperationName = "CheckData",<br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Proxy = proxy <br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Result = result <br />
    /// &nbsp; &nbsp; &nbsp; } <br />
    /// &nbsp; &nbsp; } <br /> 
    /// &nbsp; }; <br />
    /// </code>
    /// </summary>
    /// <typeparam name="TResult">Type of the method result</typeparam>
    [ToolboxBitmapAttribute(typeof(EntryPoint), "Resources.WcfOperationInvokerToolbox.bmp")]
    [Designer(typeof(WcfOperationInvokerDesigner))]
    [Description("Invoke a Operation Service Method with a result of type TResult")]
    public class WcfOperationInvoker<TResult> : AWcfOperationInvoker
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="WcfOperationInvoker{TResult}"/> class.
        /// </summary>
        public WcfOperationInvoker()
            : this(String.Empty, GetDefaultConstraints())
        {
            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WcfOperationInvoker{TResult}"/> class.
        /// </summary>
        /// <param name="opertationName">Name of the opertation.</param>
        /// <param name="constraints">The constraints.</param>
        public WcfOperationInvoker(string opertationName, params Constraint[] constraints)
        {
            Parameters = new Dictionary<string, InArgument>();
            OperationName = opertationName;
            foreach (Constraint constraint in constraints)
                this.Constraints.Add(constraint);
        }

        private static Constraint[] GetDefaultConstraints()
        {
            return new Constraint[]
            {
                 new BeInsideConstraint<WcfOperationInvoker<TResult>>(false, "Proxy", typeof(IWcfProxyActivity)).GetConstraint()
            };
        }

        /// <summary>
        /// Define the invoker catches
        /// <remarks>
        /// Don't forget to rethrow an exception if you don't want retry the invoke process
        /// </remarks>
        /// </summary>
        protected List<Catch> _invokercatches = new List<Catch>();

        private ActivityFunc<AWcfProxy, object[], TResult> _invokerActivity = null;
        
        /// <summary>
        /// Get or Set the WCF Proxy
        /// <remarks>
        /// Required Argument
        /// </remarks>
        /// </summary>
        [Browsable(true)]
        [RequiredArgument]
        [Category("Configuration")]
        [Description("Get or Set the WCF Proxy")]
        public override InArgument<AWcfProxy> Proxy { get; set; }

        /// <summary>
        /// Get or Set the Operation Name
        /// <remarks>
        /// Required Argument
        /// </remarks>
        /// </summary>
        [Browsable(true)]
        [RequiredArgument]
        [Category("Operation")]
        [Description("Get or Set the Operation Name")]
        public virtual string OperationName { get; set; }

        /// <summary>
        /// Get or Set the Parameter of the operation Method
        /// </summary>
        [Browsable(true)]
        [Category("Operation")]
        [Editor(typeof(DictionaryEditor), typeof(PropertyValueEditor))]
        [Description("Get or Set the Parameter of the Operation Method")]
        public virtual IDictionary<string, InArgument> Parameters { get; set; }

        /// <summary>
        /// Get or the the result of the operation Method
        /// </summary>
        [Browsable(true)]
        [Category("Operation")]
        [Description("Get or the the result of the operation Method")]
        public OutArgument<TResult> Result { get; set; }

        /// <summary>
        /// Creates and validates a description of the activity’s arguments, variables, child activities, and activity delegates.
        /// </summary>
        /// <param name="metadata">The activity’s metadata that encapsulates the activity’s arguments, variables, child activities, and activity delegates.</param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            RuntimeArgument valuesArgument = new RuntimeArgument("Proxy", typeof(AWcfProxy), ArgumentDirection.In, true);
            metadata.Bind(Proxy, valuesArgument);
            metadata.AddArgument(valuesArgument);

            foreach(Constraint constraint in GetCacheConstraints())
                this.Constraints.Add(constraint);

            foreach (var parameter in Parameters)
            {
                RuntimeArgument paramArgument = new RuntimeArgument(parameter.Key, parameter.Value.ArgumentType, ArgumentDirection.In);
                metadata.Bind(parameter.Value, paramArgument);
                metadata.AddArgument(paramArgument);
            }

            ImplementInvoker();

            metadata.AddImplementationDelegate(_invokerActivity);

            base.CacheMetadata(metadata);
        }

        private void ImplementInvoker()
        {
            Variable<bool> invokeKO = new Variable<bool>("invokeKO", true);

            _invokerActivity = new ActivityFunc<AWcfProxy, object[], TResult>();
            _invokerActivity.Argument1 = new DelegateInArgument<AWcfProxy>();
            _invokerActivity.Argument2 = new DelegateInArgument<object[]>();
            _invokerActivity.Result = new DelegateOutArgument<TResult>();

            InvokeMethod im = new InvokeMethod()
            {
                MethodName = "CallOperation",
                TargetObject = InArgument<AWcfProxy>.FromDelegateArgument(_invokerActivity.Argument1),
                Result = new OutArgument<TResult>(_invokerActivity.Result)
            };
            im.GenericTypeArguments.Add(typeof(TResult));
            im.Parameters.Add(new InArgument<string>(OperationName));
            im.Parameters.Add(InArgument<object[]>.FromDelegateArgument(_invokerActivity.Argument2));

            TryCatch tryCatch = new TryCatch()
            {
                Try = new Sequence()
                {
                    Activities =
                    {
                        im,
                        new Assign<bool>()
                        {
                            To = invokeKO,
                            Value = false
                        }
                    }
                }
            };
            if(_invokercatches.Count == 0)
                tryCatch.Catches.Add(new Catch<Exception>()
                    {
                        Action = new ActivityAction<Exception>()
                        {
                            Handler = new Rethrow()
                        }
                    });
            else
                foreach (var invokercatch in _invokercatches)
                    tryCatch.Catches.Add(invokercatch);

            _invokerActivity.Handler = new Sequence()
            {
                Variables = { invokeKO },
                Activities = 
                {
                    new While()
                    {
                        Condition = invokeKO,
                        Body = tryCatch
                    }
                }
            };             

        }

        protected virtual Constraint[] GetCacheConstraints()
        {
            return new Constraint[]
            {
                new WcfOperationInvokerConstraint<WcfOperationInvoker<TResult>>(false, "Operation", OperationName,
                        Parameters.ToDictionary((KV) => KV.Key, (KV) => KV.Value.ArgumentType)).GetConstraint()
            };
        }

        /// <summary>
        /// Runs the activity’s execution logic.
        /// </summary>
        /// <param name="context">The execution context in which the activity executes</param>
        protected override void Execute(NativeActivityContext context)
        {
            AWcfProxy proxy = Proxy.Get(context);

            try
            {
                context.ScheduleFunc<AWcfProxy, object[], TResult>(_invokerActivity, proxy, Parameters.Select((KV) => KV.Value.Get(context)).ToArray(), OnInvokeComplete);
            }
            catch (Exception exc)
            {
                if (exc.InnerException != null)
                    throw exc.InnerException;
                else
                    throw;
            }

        }

        private void OnInvokeComplete(NativeActivityContext context, ActivityInstance completedInstance, TResult result)
        {
            Result.Set(context, result);
        }



    }
}
